﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace FFmpegVideoClip.Lib.Controls
{
    internal class TimeLineThumbs : TickBar
    {
        public class WriteableBitmapCollection : IEnumerable<WriteableBitmap>, IEnumerable
        {
            public static readonly Int32Rect SizeRectZero = new Int32Rect(0, 0, 0, 0);
            public readonly TimeLineThumbs Owner;
            private List<WriteableBitmap> m_bitmapList = new List<WriteableBitmap>();
            public WriteableBitmap[] BitmapSources => Count == 0 ? [] : m_bitmapList.Slice(0, Count).ToArray();
            public Int32Rect SizeRect { get; private set; } = SizeRectZero;
            public int Count { get; private set; }
            public WriteableBitmapCollection(TimeLineThumbs timeLineThumbs)
            {
                Owner = timeLineThumbs;
            }
            public WriteableBitmap this[int index]
            {
                get
                {
                    if (index < 0 || index >= Count)
                        throw new ArgumentOutOfRangeException("index");
                    if (Count < 1)
                        throw new NullReferenceException("当前集合中没有实例");
                    return BitmapSources[index];
                }
            }
            public void Clear() { Count = 0; }
            public IEnumerator<WriteableBitmap> GetEnumerator()
            {
                for (int i = 0; i < Count; i++)
                    yield return m_bitmapList[i];
            }
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
            public void TrimExcess()
            {
                if (Count > 0)
                    m_bitmapList = m_bitmapList[..Count];
                else
                    m_bitmapList.Clear();
                m_bitmapList.TrimExcess();
            }
            public void SetPrefabs(int PrefabsCount)
            {
                int w = Owner.CalculateThumbWidth();
                if (w <= 0)
                    throw new InvalidOperationException("没有打开视频，无法进行操作");

                Int32Rect CurrentSizeRect = new Int32Rect(0, 0, w, (int)Owner.ActualHeight);
                if (SizeRect != CurrentSizeRect)
                {
                    m_bitmapList.Clear();
                    Count = 0;
                    SizeRect = CurrentSizeRect;
                }

                if (PrefabsCount == Count)
                    return;
                if (PrefabsCount < Count)
                {
                    Count = PrefabsCount;
                    return;
                }

                PrefabsCount -= Count;
                DpiScale dpi = VisualTreeHelper.GetDpi(Owner);
                WriteableBitmap bitmap;
                byte[] bytes = new byte[CurrentSizeRect.Width * CurrentSizeRect.Height * 3];
                int stride = CurrentSizeRect.Width * 3;
                for (int i = 0; i < PrefabsCount; i++)
                {
                    bitmap = new WriteableBitmap(w, (int)Owner.ActualHeight, dpi.PixelsPerDip, dpi.PixelsPerDip, PixelFormats.Rgb24, null);

                    bitmap.WritePixels(CurrentSizeRect, bytes, stride, 0);
                    m_bitmapList.Add(bitmap);
                    Count++;
                }
            }
        }
        private const int WM_EXITSIZEMOVE = 0x0232;
        private string FilePath = string.Empty;
        private TimeLineSlider? m_TimeLineSlider;
        private DependencyPropertyDescriptor? FilePathPropertyDescriptor, CurrentZoomMinTimeDescriptor, CurrentZoomMaxTimeDescriptor;
        private WriteableBitmapCollection WriteableBitmaps;
        private BitmapSource[] BitmapSources = [];
        private ThumbCancelSource? CancelSource;
        private bool IsUpdate = false, SizeChangedFlag = false;
        private bool IsVideoStream => m_MediaElementConcrol != null ? m_MediaElementConcrol.HasVideo : false;
        private WindowState LastWindowState = WindowState.Normal;
        private FFmpegExtractFrames? m_FFmpegExtractFrames;
        //private FFME_ExtractFrames? m_FFmpegExtractFrames;
        private MediaElementConcrol? m_MediaElementConcrol;
        public TimeLineThumbs() : base() { Loaded += TimeLineThumbs_Loaded; WriteableBitmaps = new(this); }
        private void TimeLineThumbs_Loaded(object sender, RoutedEventArgs e)
        {
            Loaded -= TimeLineThumbs_Loaded;

            if (!Tools.GetParent(this, out m_TimeLineSlider))
                throw new InitializationException($"类型[{GetType().FullName}]的实例所在的父级中不存在[{typeof(TimeLineSlider).FullName}]");
            if (DesignerProperties.GetIsInDesignMode(this))
                return;

            FilePathPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(TimeLineSlider.FilePathProperty, typeof(TimeLineSlider));
            FilePathPropertyDescriptor.AddValueChanged(m_TimeLineSlider, OnFilePathChange);

            CurrentZoomMinTimeDescriptor = DependencyPropertyDescriptor.FromProperty(TimeLineSlider.CurrentZoomMinTimeProperty, typeof(TimeLineSlider));
            CurrentZoomMinTimeDescriptor.AddValueChanged(m_TimeLineSlider, OnZoomMinTimeChange);

            CurrentZoomMaxTimeDescriptor = DependencyPropertyDescriptor.FromProperty(TimeLineSlider.CurrentZoomMaxTimeProperty, typeof(TimeLineSlider));
            CurrentZoomMaxTimeDescriptor.AddValueChanged(m_TimeLineSlider, OnZoomMaxTimeChange);

            m_TimeLineSlider!.WavSpectrogramUpdateEvent += M_TimeLineSlider_WavSpectrogramUpdateEvent;

            MainWindow mw = (MainWindow)Application.Current.MainWindow;

            m_MediaElementConcrol = mw.Mec;
            SizeChanged += TimeLineThumbs_SizeChanged;
            if (PresentationSource.FromVisual(this) is HwndSource hwndSource)
                hwndSource.AddHook(new HwndSourceHook(wndproc));

            mw.StateChanged += MainWindow_StateChanged;
        }

        private nint wndproc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled)
        {
            if (SizeChangedFlag && msg == WM_EXITSIZEMOVE)
            {
                SizeChangedFlag = false;
                SizeUpdate();
            }
            return nint.Zero;
        }
        private void TimeLineThumbs_SizeChanged(object sender, SizeChangedEventArgs e) { SizeChangedFlag = true; }
        private async void MainWindow_StateChanged(object? sender, EventArgs e)
        {
            MainWindow mw = (MainWindow)sender!;
            switch (mw.WindowState)
            {
                case WindowState.Normal:
                    if (LastWindowState == WindowState.Maximized)
                        SizeUpdate();
                    break;
                case WindowState.Maximized:
                    await Task.Run(() => Task.Delay(1000).Wait());
                    SizeUpdate();
                    break;
                default:
                    break;
            }
            LastWindowState = mw.WindowState;
        }

        private void SizeUpdate()
        {
            if (!EnableThumbImage || !((MainWindow)Application.Current.MainWindow).Mec.IsOpen || !IsVideoStream)
                return;
            WriteableBitmaps.SetPrefabs(CalculateThumbsCount());
            CallLoad();
        }
        private void OnFilePathChange(object? sender, EventArgs e) 
        {
            FilePath = m_TimeLineSlider!.FilePath;
            if(!string.IsNullOrEmpty(FilePath) && File.Exists(FilePath))
            {
                if (IsVideoStream && EnableThumbImage)
                {
                    WriteableBitmaps.SetPrefabs(CalculateThumbsCount());
                    try { m_FFmpegExtractFrames = new(FilePath, WriteableBitmaps.SizeRect); }
                    catch (Exception ex) { LogManage.WriteExceptionInfo("缩略图抽帧失败:", ex, true); }
                }
                else Close();
            }
            else Close();
        }


        private void OnZoomMinTimeChange(object? sender, EventArgs e) => CurrentZoomMinTime = sender == null ? TimeSpan.Zero : ((TimeLineSlider)sender).CurrentZoomMinTime;
        private void OnZoomMaxTimeChange(object? sender, EventArgs e) => CurrentZoomMaxTime = sender == null ? TimeSpan.Zero : ((TimeLineSlider)sender).CurrentZoomMaxTime;
        [Bindable(true)] public TimeSpan CurrentZoomMinTime { get => (TimeSpan)GetValue(CurrentZoomMinTimeProperty); set => SetValue(CurrentZoomMinTimeProperty, value); }
        public static readonly DependencyProperty CurrentZoomMinTimeProperty = DependencyProperty.Register(nameof(CurrentZoomMinTime), typeof(TimeSpan), typeof(TimeLineThumbs), new PropertyMetadata(TimeSpan.Zero, (s, e) =>
        {
            TimeLineThumbs TickMarks = (TimeLineThumbs)s;
            TimeSpan ts = (TimeSpan)e.NewValue;
            if (TickMarks.Minimum != ts.TotalSeconds)
                TickMarks.Minimum = ts.TotalSeconds;
        }));


        [Bindable(true)] public TimeSpan CurrentZoomMaxTime { get => (TimeSpan)GetValue(CurrentZoomMaxTimeProperty); set => SetValue(CurrentZoomMaxTimeProperty, value); }
        public static readonly DependencyProperty CurrentZoomMaxTimeProperty = DependencyProperty.Register(nameof(CurrentZoomMaxTime), typeof(TimeSpan), typeof(TimeLineThumbs), new PropertyMetadata(TimeSpan.Zero, (s, e) =>
        {
            TimeLineThumbs TickMarks = (TimeLineThumbs)s;
            TimeSpan ts = (TimeSpan)e.NewValue;
            if (TickMarks.Maximum != ts.TotalSeconds)
                TickMarks.Maximum = ts.TotalSeconds;
        }));


        public bool EnableThumbImage { get => (bool)GetValue(EnableThumbImageProperty); set => SetValue(EnableThumbImageProperty, value); }
        public static readonly DependencyProperty EnableThumbImageProperty = DependencyProperty.Register(nameof(EnableThumbImage), typeof(bool), typeof(TimeLineThumbs), new PropertyMetadata(false, (s, e) =>
        {
            TimeLineThumbs Thumbs = (TimeLineThumbs)s;
            if ((bool)e.NewValue)
            {
                try
                {
                    Thumbs.WriteableBitmaps.SetPrefabs(Thumbs.CalculateThumbsCount());
                    if (Thumbs.m_FFmpegExtractFrames == null)
                        Thumbs.m_FFmpegExtractFrames = new(Thumbs.FilePath, Thumbs.WriteableBitmaps.SizeRect);
                    
                }
                catch(Exception ex) { LogManage.WriteExceptionInfo("缩略图抽帧失败:",ex,true); }
                finally { Thumbs.CallLoad(); }
            }
            else Thumbs.Close();
        }));
        

        private void ClearCancelSource()
        {
            if (CancelSource != null && !CancelSource!.IsClear)
            {
                if (!CancelSource.IsCancellationRequested)
                    CancelSource.Cancel();
                CancelSource.Clear();
            }
        }
        private void ConfigCancelSource() { ClearCancelSource(); CancelSource = ThumbCancelSource.Create(); }


        private void M_TimeLineSlider_WavSpectrogramUpdateEvent(TimeLineSlider sender, RoutedEventArgs e) { CallLoad(); }
        
        protected override void OnRender(DrawingContext dc)
        {
            if (!IsUpdate || !EnableThumbImage || !((MainWindow)Application.Current.MainWindow).Mec.IsOpen)
                return;
            if (WriteableBitmaps.Count < 1)
                return;
            double CurrentPos = 0;
            foreach (WriteableBitmap bitmap in WriteableBitmaps)
            {
                dc.DrawImage(bitmap, new Rect(new Point(CurrentPos, 0), new Point(CurrentPos + WriteableBitmaps.SizeRect.Width, ActualHeight)));
                CurrentPos += WriteableBitmaps.SizeRect.Width;
            }

            //if (BitmapSources.Length < 1)
            //    return;
            //double CurrentPos = 0;
            //foreach (BitmapSource bitmap in BitmapSources)
            //{
            //    dc.DrawImage(bitmap, new Rect(new Point(CurrentPos, 0), new Point(CurrentPos + m_FFmpegExtractFrames!.destinationSize.Width, ActualHeight)));
            //    CurrentPos += m_FFmpegExtractFrames!.destinationSize.Width;
            //}

            IsUpdate = false;
        }

        private double UIPositionToTime(double UIPosition)//[MethodImpl(MethodImplOptions.AggressiveInlining)]
        {
            double CurrentTimeRangeDuration = Maximum - Minimum;
            return Minimum + CurrentTimeRangeDuration * (UIPosition / ActualWidth);
        }
        private double UIWidthToTimeDuration(double UIWidth)//[MethodImpl(MethodImplOptions.AggressiveInlining)]
        {
            double TimeDuration = Maximum - Minimum;
            return TimeDuration * (UIWidth / ActualWidth);
        }

        private int CalculateThumbWidth()
        {
            MediaElementConcrol mec = ((MainWindow)Application.Current.MainWindow).Mec;
            if (!mec.IsOpen)
                return 0;
            return (int)(ActualHeight * mec.VideoAspectRatioValue);
        }
        private int CalculateThumbsCount()
        {
            int ThumbWidth = CalculateThumbWidth();
            if( ThumbWidth <= 0 )
                return 0;
            return (int)ActualWidth / ThumbWidth + 1;
        }


        private async Task Load()
        {
            MediaElementConcrol mec = ((MainWindow)Application.Current.MainWindow).Mec;
            if (string.IsNullOrEmpty(FilePath) || !File.Exists(FilePath))
            {
                string path = mec.CurrentMediaPath;
                if (!string.IsNullOrEmpty(path) && File.Exists(path))
                    FilePath = path;
                else
                    throw new FileNotFoundException($"文件无法打开:[{FilePath}]");
            }
            ConfigCancelSource();

            TimeSpan TimeIntervalSecond = TimeSpan.FromSeconds(UIWidthToTimeDuration(WriteableBitmaps.SizeRect.Width));
            TimeSpan[] times = new TimeSpan[WriteableBitmaps.Count];
            TimeSpan CurrentTime = TimeSpan.FromSeconds(Minimum);
            times[0] = CurrentTime; ;
            for (int i = 1; i < times.Length; i++)
                times[i] = CurrentTime += TimeIntervalSecond;
           

            await Task.Run(() => m_FFmpegExtractFrames?.ExtractFrames(times, WriteableBitmaps, CancelSource!));
            IsUpdate = true;
            InvalidateVisual();

            //await m_FFmpegExtractFrames!.ExtractFramesAsync(times, CancelSource!, (result) =>
            //{
            //    BitmapSources = result;
            //    IsUpdate = true;
            //    Dispatcher.BeginInvoke(InvalidateVisual);
            //});

        }

        private void CallLoad() { if (EnableThumbImage && ((MainWindow)Application.Current.MainWindow).Mec.IsOpen && IsVideoStream) 
                _ = Tools.DelayedExecution(() => _ = Load(), UseMainThread: true); }


        private void Close()
        {
            ThumbCancelSource.CancelAll();
            m_FFmpegExtractFrames?.Dispose();
            m_FFmpegExtractFrames = null;
            WriteableBitmaps.Clear();
            WriteableBitmaps.TrimExcess();
            InvalidateVisual();
        }

        [Serializable]
        public class InitializationException : Exception
        {
            public InitializationException() { }
            public InitializationException(string message) : base(message) { }
            public InitializationException(string message, Exception inner) : base(message, inner) { }
        }
    }
}
